package com.gm.commonlib.network.basenetwork.ssl;

/*Copyright 2015 Bhavit Singh Sengar
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.*/

import android.annotation.TargetApi;
import android.os.Build;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
import java.util.HashSet;

import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

/**
 *  8443/tcp open https-alt
 * | ssl-enum-ciphers:
 * | TLSv1.0:
 * | ciphers:
 * | TLS_DHE_RSA_WITH_AES_128_CBC_SHA (dh 768) - C
 * | TLS_DHE_RSA_WITH_AES_256_CBC_SHA (dh 768) - B
 * | TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (secp160k1) - A
 * | TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (secp160k1) - A
 * | TLS_RSA_WITH_3DES_EDE_CBC_SHA (rsa 2048) - C
 * | TLS_RSA_WITH_AES_128_CBC_SHA (rsa 2048) - A
 * | TLS_RSA_WITH_AES_256_CBC_SHA (rsa 2048) - A
 * | compressors:
 * | NULL
 * | cipher preference: client
 * | warnings:
 * | 64-bit block cipher 3DES vulnerable to SWEET32 attack
 * | Key exchange (dh 768) of lower strength than certificate key
 * | Key exchange (secp160k1) of lower strength than certificate key
 * | TLSv1.1:
 * | ciphers:
 * | TLS_DHE_RSA_WITH_AES_128_CBC_SHA (dh 768) - C
 * | TLS_DHE_RSA_WITH_AES_256_CBC_SHA (dh 768) - B
 * | TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (secp160k1) - A
 * | TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (secp160k1) - A
 * | TLS_RSA_WITH_3DES_EDE_CBC_SHA (rsa 2048) - C
 * | TLS_RSA_WITH_AES_128_CBC_SHA (rsa 2048) - A
 * | TLS_RSA_WITH_AES_256_CBC_SHA (rsa 2048) - A
 * | compressors:
 * | NULL
 * | cipher preference: client
 * | warnings:
 * | 64-bit block cipher 3DES vulnerable to SWEET32 attack
 * | Key exchange (dh 768) of lower strength than certificate key
 * | Key exchange (secp160k1) of lower strength than certificate key
 * | TLSv1.2:
 * | ciphers:
 * | TLS_DHE_RSA_WITH_AES_128_CBC_SHA (dh 768) - C
 * | TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 (dh 768) - C
 * | TLS_DHE_RSA_WITH_AES_256_CBC_SHA (dh 768) - B
 * | TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 (dh 768) - B
 * | TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (secp160k1) - A
 * | TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 (secp160k1) - A
 * | TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (secp160k1) - A
 * | TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 (secp160k1) - A
 * | TLS_RSA_WITH_3DES_EDE_CBC_SHA (rsa 2048) - C
 * | TLS_RSA_WITH_AES_128_CBC_SHA (rsa 2048) - A
 * | TLS_RSA_WITH_AES_128_CBC_SHA256 (rsa 2048) - A
 * | TLS_RSA_WITH_AES_256_CBC_SHA (rsa 2048) - A | TLS_RSA_WITH_AES_256_CBC_SHA256 (rsa 2048) - A
 * | compressors:
 * | NULL
 * | cipher preference: client
 * | warnings:
 * | 64-bit block cipher 3DES vulnerable to SWEET32 attack
 * | Key exchange (dh 768) of lower strength than certificate key
 * | Key exchange (secp160k1) of lower strength than certificate key
 * |_ least strength: C
 *
 * {@link ;}
 */
public class NoSSLv3SocketFactory extends SSLSocketFactory {

    private static HashSet<String> sSupportedProtocols = new HashSet<>();
    private static HashSet<String> EnabledProtocols = new HashSet<>();

    private static HashSet<String> sSupportedCiphers = new HashSet<>();
    private static HashSet<String> EnabledCipherList = new HashSet<>();

    private final static String[] sEnabledProtocols;
    private final static String[] sEnabledCiphers;

    static {
        HashSet<String> supportCiphers = new HashSet<>();
        //TODO 操作的方法自API级别9以来已使用密码套件的标准名称，
        //TODO 如下表所示。在API级别9之前，已经使用了非标准（OpenSSL）名称（请参见下表）。
        switch (Build.VERSION.SDK_INT) {
            case Build.VERSION_CODES.O_MR1://TODO 27;
            case Build.VERSION_CODES.O://TODO 26;
            case Build.VERSION_CODES.N_MR1://TODO 25;
            case Build.VERSION_CODES.N://TODO 24;
                supportCiphers.add("TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256");
                supportCiphers.add("TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256");
                supportCiphers.add("TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256");
            case Build.VERSION_CODES.M://TODO 23;
            case Build.VERSION_CODES.LOLLIPOP_MR1://TODO 22;
            case Build.VERSION_CODES.LOLLIPOP://TODO 21;
                supportCiphers.add("TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA");
                supportCiphers.add("TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA");
                supportCiphers.add("TLS_FALLBACK_SCSV");
                supportCiphers.add("TLS_PSK_WITH_AES_128_CBC_SHA");
                supportCiphers.add("TLS_PSK_WITH_AES_256_CBC_SHA");
            case Build.VERSION_CODES.KITKAT_WATCH://TODO 20;
                supportCiphers.add("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256");
                supportCiphers.add("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256");
                supportCiphers.add("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384");
                supportCiphers.add("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384");
                supportCiphers.add("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256");
                supportCiphers.add("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256");
                supportCiphers.add("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384");
                supportCiphers.add("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384");
                supportCiphers.add("TLS_RSA_WITH_AES_128_CBC_SHA256");
                supportCiphers.add("TLS_RSA_WITH_AES_128_GCM_SHA256");
                supportCiphers.add("TLS_RSA_WITH_AES_256_CBC_SHA256");
                supportCiphers.add("TLS_RSA_WITH_AES_256_GCM_SHA384");
                supportCiphers.add("TLS_DHE_RSA_WITH_AES_128_GCM_SHA256");
            case Build.VERSION_CODES.KITKAT://TODO 19;
            case Build.VERSION_CODES.JELLY_BEAN_MR2://TODO 18;
            case Build.VERSION_CODES.JELLY_BEAN_MR1://TODO 17;
            case Build.VERSION_CODES.JELLY_BEAN://TODO 16
            case Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1://TODO 15;
            case Build.VERSION_CODES.ICE_CREAM_SANDWICH://TODO 14;
            case Build.VERSION_CODES.HONEYCOMB_MR2://TODO 13;
            case Build.VERSION_CODES.HONEYCOMB_MR1://TODO 12;
            case Build.VERSION_CODES.HONEYCOMB://TODO 11;
                supportCiphers.add("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA");
                supportCiphers.add("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA");
                supportCiphers.add("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA");
                supportCiphers.add("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA");
                supportCiphers.add("TLS_EMPTY_RENEGOTIATION_INFO_SCSV");
            case Build.VERSION_CODES.GINGERBREAD_MR1://TODO 10;
            case Build.VERSION_CODES.GINGERBREAD://TODO 9;
                supportCiphers.add("TLS_RSA_WITH_AES_128_CBC_SHA");
                supportCiphers.add("TLS_RSA_WITH_AES_256_CBC_SHA");
                supportCiphers.add("SSL_RSA_WITH_3DES_EDE_CBC_SHA");

                supportCiphers.add("TLS_DHE_RSA_WITH_AES_128_CBC_SHA");//TODO 1+
                supportCiphers.add("TLS_DHE_RSA_WITH_AES_256_CBC_SHA");//TODO 1+
                supportCiphers.add("TLS_RSA_WITH_3DES_EDE_CBC_SHA");//TODO 1+
                break;

            case Build.VERSION_CODES.FROYO://TODO 8;
            case Build.VERSION_CODES.ECLAIR_MR1://TODO 7;
            case Build.VERSION_CODES.ECLAIR_0_1://TODO 6;
            case Build.VERSION_CODES.ECLAIR://TODO 5;
            case Build.VERSION_CODES.DONUT://TODO 4;
            case Build.VERSION_CODES.CUPCAKE://TODO 3;
            case Build.VERSION_CODES.BASE_1_1://TODO 2;
            case Build.VERSION_CODES.BASE://TODO 1;
                supportCiphers.add("TLS_RSA_WITH_AES_128_CBC_SHA");
                supportCiphers.add("TLS_RSA_WITH_AES_256_CBC_SHA");
                supportCiphers.add("TLS_DHE_RSA_WITH_AES_128_CBC_SHA");
                supportCiphers.add("TLS_DHE_RSA_WITH_AES_256_CBC_SHA");
                supportCiphers.add("TLS_RSA_WITH_3DES_EDE_CBC_SHA");//TODO 1+

                supportCiphers.add("SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5");
                supportCiphers.add("SSL_CK_RC2_128_CBC_WITH_MD5");
                supportCiphers.add("SSL_CK_DES_64_CBC_WITH_MD5");
                supportCiphers.add("SSL_CK_DES_192_EDE3_CBC_WITH_MD5");
                supportCiphers.add("SSL_RSA_WITH_3DES_EDE_CBC_SHA");
        }

        //TODO 去掉不支持的套件
        switch (Build.VERSION.SDK_INT) {
            case Build.VERSION_CODES.O_MR1://TODO 27;
            case Build.VERSION_CODES.O://TODO 26;
                supportCiphers.remove("TLS_DHE_RSA_WITH_AES_128_GCM_SHA256");
            case Build.VERSION_CODES.N_MR1://TODO 25;
            case Build.VERSION_CODES.N://TODO 24;
            case Build.VERSION_CODES.M://TODO 23;
            case Build.VERSION_CODES.LOLLIPOP_MR1://TODO 22;
            case Build.VERSION_CODES.LOLLIPOP://TODO 21;
            case Build.VERSION_CODES.KITKAT_WATCH://TODO 20;
                supportCiphers.remove("SSL_RSA_WITH_3DES_EDE_CBC_SHA");
            case Build.VERSION_CODES.KITKAT://TODO 19;
            case Build.VERSION_CODES.JELLY_BEAN_MR2://TODO 18;
            case Build.VERSION_CODES.JELLY_BEAN_MR1://TODO 17;
            case Build.VERSION_CODES.JELLY_BEAN://TODO 16
            case Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1://TODO 15;
            case Build.VERSION_CODES.ICE_CREAM_SANDWICH://TODO 14;
            case Build.VERSION_CODES.HONEYCOMB_MR2://TODO 13;
            case Build.VERSION_CODES.HONEYCOMB_MR1://TODO 12;
            case Build.VERSION_CODES.HONEYCOMB://TODO 11;
            case Build.VERSION_CODES.GINGERBREAD_MR1://TODO 10;
            case Build.VERSION_CODES.GINGERBREAD://TODO 9;
                supportCiphers.remove("SSL_CK_DES_64_CBC_WITH_MD5");
                supportCiphers.remove("SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5");
                supportCiphers.remove("SSL_CK_RC2_128_CBC_WITH_MD5");
            case Build.VERSION_CODES.FROYO://TODO 8;
            case Build.VERSION_CODES.ECLAIR_MR1://TODO 7;
            case Build.VERSION_CODES.ECLAIR_0_1://TODO 6;
            case Build.VERSION_CODES.ECLAIR://TODO 5;
            case Build.VERSION_CODES.DONUT://TODO 4;
            case Build.VERSION_CODES.CUPCAKE://TODO 3;
            case Build.VERSION_CODES.BASE_1_1://TODO 2;
            case Build.VERSION_CODES.BASE://TODO 1;
        }

        EnabledProtocols.add("TLSv1");
        EnabledProtocols.add("TLSv1.1");
        EnabledProtocols.add("TLSv1.2");
        try {
            SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault().createSocket();
            if (socket != null) {
                sSupportedProtocols.addAll(Arrays.asList(socket.getSupportedProtocols()));
                sSupportedCiphers.addAll(Arrays.asList(socket.getSupportedCipherSuites()));
                EnabledCipherList.addAll(Arrays.asList(socket.getEnabledCipherSuites()));
            } else {
                throw new Exception("createSocket socket null");
            }

            EnabledProtocols.retainAll(sSupportedProtocols);

            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
                supportCiphers.retainAll(sSupportedCiphers);
                EnabledCipherList.addAll(supportCiphers);
            }
        } catch (Exception e) {
            e.printStackTrace();
            EnabledCipherList.addAll(supportCiphers);
        }

        sEnabledProtocols = EnabledProtocols.toArray(new String[0]);
        sEnabledCiphers = EnabledCipherList.toArray(new String[0]);
    }

    private final SSLSocketFactory delegate;

    public NoSSLv3SocketFactory() {
        this.delegate = HttpsURLConnection.getDefaultSSLSocketFactory();
    }

    public NoSSLv3SocketFactory(SSLSocketFactory delegate) {
        this.delegate = delegate;
    }

    @Override
    public String[] getDefaultCipherSuites() {
        return delegate.getDefaultCipherSuites();
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return delegate.getSupportedCipherSuites();
    }

    @Override
    public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
        return makeSocketSafe(delegate.createSocket(s, host, port, autoClose));
    }

    @Override
    public Socket createSocket(String host, int port) throws IOException {
        return makeSocketSafe(delegate.createSocket(host, port));
    }

    @Override
    public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
        return makeSocketSafe(delegate.createSocket(host, port, localHost, localPort));
    }

    @Override
    public Socket createSocket(InetAddress host, int port) throws IOException {
        return makeSocketSafe(delegate.createSocket(host, port));
    }

    @Override
    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
        return makeSocketSafe(delegate.createSocket(address, port, localAddress, localPort));
    }

    private Socket makeSocketSafe(Socket socket) {
        if (socket instanceof SSLSocket) {
            socket = new NoSSLv3SSLSocket((SSLSocket) socket);
        }
        return socket;
    }

    private class NoSSLv3SSLSocket extends DelegateSSLSocket {

        private NoSSLv3SSLSocket(SSLSocket delegate) {
            super(delegate);
        }

        @Override
        public void setEnabledProtocols(String[] protocols) {
            //仅能设置服务器支持的加密规范
            super.setEnabledProtocols(sEnabledProtocols);
        }

        @Override
        public void setEnabledCipherSuites(String[] suites) {
            super.setEnabledCipherSuites(sSupportedCiphers.toArray(new String[0]));
        }
    }

    public class DelegateSSLSocket extends SSLSocket {
        protected final SSLSocket delegate;

        DelegateSSLSocket(SSLSocket delegate) {
            this.delegate = delegate;
        }

        @TargetApi(24)
        @Override
        public SSLSession getHandshakeSession() {
            return delegate.getHandshakeSession();
        }

        @Override
        public SSLParameters getSSLParameters() {
            return delegate.getSSLParameters();
        }

        @Override
        public void setSSLParameters(SSLParameters params) {
            delegate.setSSLParameters(params);
        }

        @Override
        public String[] getSupportedCipherSuites() {
            return delegate.getSupportedCipherSuites();
        }

        @Override
        public String[] getEnabledCipherSuites() {
            return delegate.getEnabledCipherSuites();
        }

        @Override
        public void setEnabledCipherSuites(String[] suites) {
            delegate.setEnabledCipherSuites(suites);
        }

        @Override
        public String[] getSupportedProtocols() {
            return delegate.getSupportedProtocols();
        }

        @Override
        public String[] getEnabledProtocols() {
            return delegate.getEnabledProtocols();
        }

        @Override
        public void setEnabledProtocols(String[] protocols) {
            delegate.setEnabledProtocols(protocols);
        }

        @Override
        public SSLSession getSession() {
            return delegate.getSession();
        }

        @Override
        public void addHandshakeCompletedListener(HandshakeCompletedListener listener) {
            delegate.addHandshakeCompletedListener(listener);
        }

        @Override
        public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) {
            delegate.removeHandshakeCompletedListener(listener);
        }

        @Override
        public void startHandshake() throws IOException {
            delegate.startHandshake();
        }

        @Override
        public void setUseClientMode(boolean mode) {
            delegate.setUseClientMode(mode);
        }

        @Override
        public boolean getUseClientMode() {
            return delegate.getUseClientMode();
        }

        @Override
        public void setNeedClientAuth(boolean need) {
            delegate.setNeedClientAuth(need);
        }

        @Override
        public void setWantClientAuth(boolean want) {
            delegate.setWantClientAuth(want);
        }

        @Override
        public boolean getNeedClientAuth() {
            return delegate.getNeedClientAuth();
        }

        @Override
        public boolean getWantClientAuth() {
            return delegate.getWantClientAuth();
        }

        @Override
        public void setEnableSessionCreation(boolean flag) {
            delegate.setEnableSessionCreation(flag);
        }

        @Override
        public boolean getEnableSessionCreation() {
            return delegate.getEnableSessionCreation();
        }

        @Override
        public void bind(SocketAddress localAddr) throws IOException {
            delegate.bind(localAddr);
        }

        @Override
        public synchronized void close() throws IOException {
            delegate.close();
        }

        @Override
        public void connect(SocketAddress remoteAddr) throws IOException {
            delegate.connect(remoteAddr);
        }

        @Override
        public void connect(SocketAddress remoteAddr, int timeout) throws IOException {
            delegate.connect(remoteAddr, timeout);
        }

        @Override
        public SocketChannel getChannel() {
            return delegate.getChannel();
        }

        @Override
        public InetAddress getInetAddress() {
            return delegate.getInetAddress();
        }

        @Override
        public InputStream getInputStream() throws IOException {
            return delegate.getInputStream();
        }

        @Override
        public boolean getKeepAlive() throws SocketException {
            return delegate.getKeepAlive();
        }

        @Override
        public InetAddress getLocalAddress() {
            return delegate.getLocalAddress();
        }

        @Override
        public int getLocalPort() {
            return delegate.getLocalPort();
        }

        @Override
        public SocketAddress getLocalSocketAddress() {
            return delegate.getLocalSocketAddress();
        }

        @Override
        public boolean getOOBInline() throws SocketException {
            return delegate.getOOBInline();
        }

        @Override
        public OutputStream getOutputStream() throws IOException {
            return delegate.getOutputStream();
        }

        @Override
        public int getPort() {
            return delegate.getPort();
        }

        @Override
        public synchronized int getReceiveBufferSize() throws SocketException {
            return delegate.getReceiveBufferSize();
        }

        @Override
        public SocketAddress getRemoteSocketAddress() {
            return delegate.getRemoteSocketAddress();
        }

        @Override
        public boolean getReuseAddress() throws SocketException {
            return delegate.getReuseAddress();
        }

        @Override
        public synchronized int getSendBufferSize() throws SocketException {
            return delegate.getSendBufferSize();
        }

        @Override
        public int getSoLinger() throws SocketException {
            return delegate.getSoLinger();
        }

        @Override
        public synchronized int getSoTimeout() throws SocketException {
            return delegate.getSoTimeout();
        }

        @Override
        public boolean getTcpNoDelay() throws SocketException {
            return delegate.getTcpNoDelay();
        }

        @Override
        public int getTrafficClass() throws SocketException {
            return delegate.getTrafficClass();
        }

        @Override
        public boolean isBound() {
            return delegate.isBound();
        }

        @Override
        public boolean isClosed() {
            return delegate.isClosed();
        }

        @Override
        public boolean isConnected() {
            return delegate.isConnected();
        }

        @Override
        public boolean isInputShutdown() {
            return delegate.isInputShutdown();
        }

        @Override
        public boolean isOutputShutdown() {
            return delegate.isOutputShutdown();
        }

        @Override
        public void sendUrgentData(int value) throws IOException {
            delegate.sendUrgentData(value);
        }

        @Override
        public void setKeepAlive(boolean keepAlive) throws SocketException {
            delegate.setKeepAlive(keepAlive);
        }

        @Override
        public void setOOBInline(boolean oobinline) throws SocketException {
            delegate.setOOBInline(oobinline);
        }

        @Override
        public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
            delegate.setPerformancePreferences(connectionTime, latency, bandwidth);
        }

        @Override
        public synchronized void setReceiveBufferSize(int size) throws SocketException {
            delegate.setReceiveBufferSize(size);
        }

        @Override
        public void setReuseAddress(boolean reuse) throws SocketException {
            delegate.setReuseAddress(reuse);
        }

        @Override
        public synchronized void setSendBufferSize(int size) throws SocketException {
            delegate.setSendBufferSize(size);
        }

        @Override
        public void setSoLinger(boolean on, int timeout) throws SocketException {
            delegate.setSoLinger(on, timeout);
        }

        @Override
        public synchronized void setSoTimeout(int timeout) throws SocketException {
            delegate.setSoTimeout(timeout);
        }

        @Override
        public void setTcpNoDelay(boolean on) throws SocketException {
            delegate.setTcpNoDelay(on);
        }

        @Override
        public void setTrafficClass(int value) throws SocketException {
            delegate.setTrafficClass(value);
        }

        @Override
        public void shutdownInput() throws IOException {
            delegate.shutdownInput();
        }

        @Override
        public void shutdownOutput() throws IOException {
            delegate.shutdownOutput();
        }

        @Override
        public String toString() {
            return delegate.toString();
        }

        @Override
        public boolean equals(Object o) {
            return delegate.equals(o);
        }
    }
}